package com.glp.work.flow.service.impl;

import com.glp.work.flow.constant.*;
import com.glp.work.flow.dto.OrderInfoDTO;
import com.glp.work.flow.dto.RedisLock;
import com.glp.work.flow.dto.request.StartProcessRequestDTO;
import com.glp.work.flow.exception.APIException;
import com.glp.work.flow.exception.WorkflowException;
import com.glp.work.flow.mapper.WfFormMapper;
import com.glp.work.flow.mapper.WfOrderMapper;
import com.glp.work.flow.mapper.WfPlatformDefMapper;
import com.glp.work.flow.mapper.WfTaskAssignMapper;
import com.glp.work.flow.pojo.WfForm;
import com.glp.work.flow.pojo.WfOrder;
import com.glp.work.flow.pojo.WfPlatformDef;
import com.glp.work.flow.request.DeleteRuningOrderRequest;
import com.glp.work.flow.request.MessageEventReceivedRequest;
import com.glp.work.flow.response.OrderHisInfoResponse;
import com.glp.work.flow.response.OrderStatusResponse;
import com.glp.work.flow.response.ProcessInitiateResponse;
import com.glp.work.flow.service.*;
import com.glp.work.flow.utils.DFVaildUtils;
import com.glp.work.flow.utils.DateUtils;
import com.glp.work.flow.utils.RedisManage;
import com.glp.work.flow.utils.VersionUtils;
import com.glp.work.flow.workflow.plugin.flowable.constant.FlowConstant;
import com.glp.work.flow.workflow.plugin.flowable.service.FlowProcessDefinitionService;
import com.glp.work.flow.workflow.plugin.flowable.service.FlowTaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ActivityInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author pengjia
 * @version 2020年3月9日 下午5:14:16
 */
@Service
@Slf4j
public class WfProcessInitiateServiceImpl implements WfProcessInitiateService {

    @Autowired
    private FlowTaskService flowTaskService;
    @Autowired
    private OrderInfoService orderInfoService;
    @Autowired
    private WfOrderMapper wfOrderMapper;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private WfFormMapper wfFormMapper;
    @Autowired
    private WfFormService wfFormService;
    @Autowired
    private WfPlatformDefMapper wfPlatformDefMapper;
    @Autowired
    private FlowProcessDefinitionService flowProcessDefinitionService;
    @Autowired
    private WfProcessService wfProcessService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private RedisManage redisManage;
    @Resource
    private UserService userService;
    @Autowired
    private WfTaskAssignMapper assignMapper;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public ProcessInitiateResponse processInitiateDoMain(StartProcessRequestDTO target) {
        WfOrder wfOrder = null;
        String processId = null;
        String wfId = null;
        ProcessInitiateResponse response = new ProcessInitiateResponse();
        {
            log.info("查询判断工单状态是否为已驳回！param={}", target);
            Example example = new Example(WfOrder.class);
            example.createCriteria()
                    .andEqualTo("orderId", target.getOrderId())
                    .andEqualTo("appId", target.getAppId())
                    .andEqualTo("procDefKey", target.getProcDefKey())
                    .andEqualTo("orderStatus", OrderStatus.WAITNEWS.getCode());
            wfOrder = wfOrderMapper.selectOneByExample(example);
        }
        if (wfOrder == null) {
            try {
                log.info("开始提交工作流引擎...{}", target.getProcDefKey());
                if (target.getProcessVariable() == null) {
                    target.setProcessVariable(new HashMap<String, Object>());
                }
                target.getProcessVariable().put(FlowConstant.APPID, target.getAppId());
                target.getProcessVariable().put(FlowConstant.ORDER_ID, target.getOrderId());
                if (CollectionUtils.isNotEmpty(target.getUserNos())) {
                    target.getProcessVariable().put(ProcessVariablesEnum.DYNAMIC_USER_ASSIGN.name(), target.getUserNos());
                }
                if (CollectionUtils.isNotEmpty(target.getDynamicUserNos())) {
                    target.getProcessVariable().put(ProcessVariablesEnum.DYNAMIC_USER_ASSIGN_ONE.name(), target.getDynamicUserNos());
                }
                processId = flowTaskService.startProcess(target.getProcDefKey(), target.getOrderId(), target.getDynamicForm(), target.getProcessVariable(), target.getUserId(), target.getBusinessScopebCode(), null);
            } catch (Exception e) {
                log.error("工作流引擎启动失败！", e);
                throw new APIException(APICode.WORKFLOW_SUMBIT_FAIL, "工作流引擎启动失败！");
            }
            log.info("提交工单数据！orderId：{}，processId：{}", target.getOrderId(), processId);
            response = submitOrderData(processId, target);
        } else {
            processId = wfOrder.getProcessId();
            log.info("工单状态为驳回，更新工单数据！orderId：{}，processId：{}", target.getOrderId(), processId);
            updateOrderData(processId, target, wfOrder);
        }
        response.setProcessId(processId);
        return response;
    }

    private void updateOrderData(String processId, StartProcessRequestDTO target, WfOrder wfOrder) {
        try {
            Execution exec = runtimeService.createExecutionQuery().processInstanceId(processId).messageEventSubscriptionName(FLMsgEventEnum.DEFAULT.getEvent()).singleResult();
            if (exec != null) {
                log.info("当前在中间消息事件节点，触发改节点事件！processId={}, executionId={}，vars={}", processId, exec.getId(), target);
                MessageEventReceivedRequest messageEventReceivedRequest = new MessageEventReceivedRequest();
                messageEventReceivedRequest.setMessageEvent(FLMsgEventEnum.DEFAULT.getEvent());
                messageEventReceivedRequest.setProcessId(processId);
                messageEventReceivedRequest.setExecutionId(exec.getId());
                messageEventReceivedRequest.setUserNo(target.getUserId());
                messageEventReceivedRequest.setProcessVariable(target.getProcessVariable());
                wfProcessService.messageEventReceived(messageEventReceivedRequest);
            }
        } catch (Exception e) {
            log.error("发送中间消息失败！", e);
            throw new APIException(APICode.DATA_ERROR, "发送中间消息失败！processId=" + processId);
        }
        {
            log.info("更新工单信息。processId={}，req={}", processId, target);
            WfOrder con1 = new WfOrder();
            con1.setProcessId(processId);
            WfOrder currentWfOrder = wfOrderMapper.selectOne(con1);
            OrderInfoDTO orderDto = new OrderInfoDTO();
            List<Task> list = taskService.createTaskQuery().processInstanceId(processId).list();
            if (CollectionUtils.isNotEmpty(list)) {
                orderDto.setTaskName(list.get(0).getName());
            }
            orderDto.setAppId(target.getAppId());
            orderDto.setProcDefKey(target.getProcDefKey());
            orderDto.setDynamicForm(target.getDynamicForm());
            orderDto.setOrderTitle(target.getOrderTitle());
            orderDto.setProcessId(processId);
            orderDto.setUserId(target.getUserId());
            orderDto.setUserName(target.getUserName());
            orderDto.setOrderId(target.getOrderId());
            orderDto.setIndex_1(target.getIndex_1());
            orderDto.setIndex_2(target.getIndex_2());
            orderDto.setIndex_3(target.getIndex_3());
            orderInfoService.updateRejectOrderToProcessing(orderDto, currentWfOrder);
        }
        {
            if (MapUtils.isNotEmpty(target.getDynamicForm())) {
                log.info("更新表单数据。processId={}，req={}", processId, target.getDynamicForm());
                wfFormService.updateFormData(processId, target.getDynamicForm());
            }
        }
    }

    private ProcessInitiateResponse submitOrderData(String processId, StartProcessRequestDTO target) {
        log.info("开始创建工单数据...{},{}", target.getProcDefKey(), processId);
        String wfId = null;
        String version = null;
        try {
            OrderInfoDTO dfDto = new OrderInfoDTO();
            dfDto.setAppId(target.getAppId());
            dfDto.setProcDefKey(target.getProcDefKey());
            dfDto.setDynamicForm(target.getDynamicForm());
            dfDto.setOrderTitle(target.getOrderTitle());
            dfDto.setProcessId(processId);
            dfDto.setUserId(target.getUserId());
            dfDto.setBatch(target.getBatch());
            dfDto.setUserName(target.getUserName());
            List<Task> list = taskService.createTaskQuery().processInstanceId(processId).list();
            if (CollectionUtils.isNotEmpty(list)) {
                Task task = list.get(0);
                if (task != null) {
                    dfDto.setCurrentNode(task.getName());
                    dfDto.setCurrentInstance(task.getTaskDefinitionKey());
                    version = VersionUtils.getVersion(task.getProcessDefinitionId());
                }
            }
            dfDto.setRemarks(target.getRemarks());
            dfDto.setOrderId(target.getOrderId());
            dfDto.setIndex_1(target.getIndex_1());
            dfDto.setIndex_2(target.getIndex_2());
            dfDto.setIndex_3(target.getIndex_3());
            dfDto.setBusinessScopebCode(target.getBusinessScopebCode());
            wfId = orderInfoService.saveOrderInfo(dfDto);
        } catch (Exception e) {
            log.error("创建工单数据失败！", e);
            throw new APIException(APICode.WORKFLOW_SUMBIT_FAIL, "创建工单数据失败！" + e);
        }
        try {
            log.info("开始保存动态表单数据...{},{}", target.getProcDefKey(), processId);
            wfFormService.initForm(target.getProcDefKey(), processId, target.getDynamicForm());
        } catch (Exception e) {
            log.error("保存动态表单数据失败！", e);
            throw new APIException(APICode.WORKFLOW_SUMBIT_FAIL, "工作流定义formKey不存在或已禁用！");
        }
        ProcessInitiateResponse response = new ProcessInitiateResponse();
        response.setWfId(wfId);
        response.setVersion(version);
        return response;
    }

    @Override
    public boolean validateDuplicavteProcess(String orderId, String appId, String procDefKey, String batch) {
        // 工单去重校验。排除驳回的工单，驳回的工单可以重新发起。  如果传入批次号，则业务单号可重复，业务单号+批次号不可重复。
        RedisLock rlk = new RedisLock();
        rlk.setKey(orderId + appId + procDefKey);
        rlk.setValue(DateUtils.formatCurrentDate(DateUtils.FORMAT_TIME));
        boolean locked = redisManage.defaultLockShortLived(rlk);
        if (locked) {
            Example example = new Example(WfOrder.class);
            Example.Criteria criteria = example.createCriteria();
            criteria.andEqualTo("orderId", orderId)
                    .andEqualTo("appId", appId)
                    .andEqualTo("procDefKey", procDefKey)
                    .andNotEqualTo("orderStatus", OrderStatus.WAITNEWS.getCode());
            if (StringUtils.isNotBlank(batch)) {
                criteria.andEqualTo("batch", batch);
            }
            List<WfOrder> wfOrders = wfOrderMapper.selectByExample(example);
            int count = wfOrderMapper.selectCountByExample(example);
            if (CollectionUtils.isNotEmpty(wfOrders)) {
                log.error("单号重复提交：{}", orderId);
                throw new WorkflowException(APICode.INVALID_PARAMETER, "单号重复提交！" + wfOrders.get(0).getProcessId());
            }
            return count == 0;
        }
        return false;
    }

    @Override
    public OrderStatusResponse queryOrderStatus(String processId) {
        WfOrder condition = new WfOrder();
        condition.setProcessId(processId);
        WfOrder record = wfOrderMapper.selectOne(condition);
        if (record != null) {
            OrderStatusResponse orderStatus = new OrderStatusResponse();
            orderStatus.setProcessId(processId);
            orderStatus.setStatus(record.getOrderStatus());
            return orderStatus;
        }
        return null;
    }

    @Override
    public List<OrderHisInfoResponse> queryHistoryRecordsByOrderId(String orderId) {
        WfOrder condition = new WfOrder();
        condition.setOrderId(orderId);
        WfOrder record = wfOrderMapper.selectOne(condition);
        List<HistoricTaskInstance> hisList = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(record.getProcessId()).finished().list();
        List<OrderHisInfoResponse> resultList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(hisList)) {
            hisList.stream().forEach((HistoricTaskInstance action) -> {
                OrderHisInfoResponse target = new OrderHisInfoResponse();
                target.setOrderId(orderId);
                target.setProcessId(action.getProcessInstanceId());
                target.setTaskId(action.getId());
                target.setTaskName(action.getName());
                target.setAssignee(action.getAssignee());
                target.setStartTime(action.getCreateTime());
                target.setEndTime(action.getEndTime());
                resultList.add(target);
            });
        }
        return resultList;
    }

    @Override
    public String validateFormData(String proDefKey, Map<String, Object> formData) {
        StringBuilder result = new StringBuilder("");
        List<WfForm> formList = queryFormByDefKey(proDefKey);
        if (CollectionUtils.isNotEmpty(formList)) {
            List<String> textList = formList.stream().map((WfForm mapper) -> {
                return mapper.getFormContent();
            }).collect(Collectors.toList());
            Map<String, String> vaildResult = DFVaildUtils.getRequiredFieldForFormMarkingJson(textList);
            for (String key : vaildResult.keySet()) {
                if ((formData.get(key) == null)
                        || (formData.get(key) instanceof String) && (StringUtils.isBlank((String) formData.get(key)))) {
                    result.append(vaildResult.get(key) + "(" + key + ")字段不能为空！，");
                }
            }
        }
        return result.toString();
    }

    private List<WfForm> queryFormByDefKey(String proDefKey) {
        List<String> formNoList = getFormKey(proDefKey);
        List<WfForm> wfFormList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(formNoList)) {
            Example example = new Example(WfForm.class);
            example.createCriteria().andIn("formCode", formNoList.stream().collect(Collectors.toSet()));
            wfFormList = wfFormMapper.selectByExample(example);
        }
        return wfFormList;
    }

    public List<String> getFormKey(String proDefKey) {
        String proDefId = repositoryService.createProcessDefinitionQuery().processDefinitionKey(proDefKey).latestVersion().singleResult().getId();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(proDefId);
        org.flowable.bpmn.model.Process process = bpmnModel.getMainProcess();
        List<UserTask> flowElementsOfType = process.findFlowElementsOfType(UserTask.class);
        List<String> formNoList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(flowElementsOfType)) {
            flowElementsOfType.stream().forEach((UserTask action) -> {
                if (StringUtils.isNotBlank(action.getFormKey())) {
                    formNoList.add(action.getFormKey());
                }
            });
        }
        return formNoList;
    }

    @Override
    public boolean validateProDefKey(String proDefKey) {
        if (proDefKey.length() > 64) {
            return false;
        }
        try {
            if (repositoryService.createProcessDefinitionQuery()
                    .processDefinitionKey(proDefKey)
                    .latestVersion()
                    .singleResult() == null) {
                return false;
            }
        } catch (Exception e) {
            log.error("根据proDefKey查询工作流失败！{}", e, proDefKey);
            return false;
        }
        return true;
    }

    @Override
    public boolean validateAppId(String appId) {
        Example example = new Example(WfPlatformDef.class);
        example.createCriteria().andEqualTo("platformId", appId);
        int count = wfPlatformDefMapper.selectCountByExample(example);
        return count > 0;
    }

    @Override
    public boolean validateAssignSetting(String procDefKey) {
        String proDefId = repositoryService.createProcessDefinitionQuery().processDefinitionKey(procDefKey).latestVersion().singleResult().getId();
        List<UserTask> userTaskList = flowProcessDefinitionService.getUserTaskListById(proDefId);
        if (CollectionUtils.isNotEmpty(userTaskList)) {
            List<String> taskIdList = userTaskList.stream().map(UserTask::getId).collect(Collectors.toList());
            Map<String, Object> map = new HashMap<>();
            map.put("taskIdList", taskIdList);
            map.put("procDefId", proDefId);
            int userOrRoleCount = assignMapper.getUserOrRoleCount(map);
            if (userOrRoleCount < 1) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void updateOrderStatus(OrderStatusResponse response) {
        Example example = new Example(WfOrder.class);
        example.createCriteria().andEqualTo("processId", response.getProcessId());
        WfOrder wfOrder = new WfOrder();
        wfOrder.setOrderStatus("10");
        wfOrderMapper.updateByExampleSelective(wfOrder, example);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String deleteRuningOrder(DeleteRuningOrderRequest deleteRuningOrderRequest) {
        Example example = new Example(WfOrder.class);
        example.createCriteria().andEqualTo("processId", deleteRuningOrderRequest.getProcessId()).
                andEqualTo("appId", deleteRuningOrderRequest.getAppId());
        WfOrder wfOrder = wfOrderMapper.selectOneByExample(example);
        if (wfOrder == null) {
            throw new APIException(APICode.WORKFLOW_PROCESS_NOT_FOUND, "此APPID下未找到流程！");
        }
        wfOrderMapper.deleteByExample(example);
        runtimeService.deleteProcessInstance(deleteRuningOrderRequest.getProcessId(), deleteRuningOrderRequest.getRemark());
        return deleteRuningOrderRequest.getProcessId();
    }

    @Override
    public String auditFailRuningOrder(DeleteRuningOrderRequest deleteRuningOrderRequest) {
        List<Execution> executions = runtimeService.createExecutionQuery().processInstanceId(deleteRuningOrderRequest.getProcessId()).list();
        if (CollectionUtils.isNotEmpty(executions)) {
            List<ActivityInstance> actIns = runtimeService.createActivityInstanceQuery().processInstanceId(deleteRuningOrderRequest.getProcessId()).unfinished().list();
            runtimeService.setVariableLocal(executions.get(0).getId(), actIns.get(0).getId(), deleteRuningOrderRequest.getRemark());
            Map<String, Object> vars = new HashMap<>();
            vars.put(FlowConstant.AUDIT_PASS, OrderOperation.FAIL.name());
            flowTaskService.endProcess(deleteRuningOrderRequest.getProcessId(), executions.get(0).getId(), vars);
            return deleteRuningOrderRequest.getProcessId();
        } else {
            throw new APIException(APICode.WORKFLOW_PROCESS_NOT_FOUND, "审批不通过执行异常，未找到运行中的Executions！当前流程实例已结束或者不活动");
        }
    }

    @Override
    public void removeOrderByProcDefKey(String prcDefKey) {
        List<Task> list = taskService.createTaskQuery().processDefinitionKeyLike(prcDefKey).list();
        List<String> collect = list.stream().map(Task::getProcessInstanceId).collect(Collectors.toList());
        for (String s : collect) {
            runtimeService.deleteProcessInstance(s, "清理垃圾数据");
        }
        Example example = new Example(WfOrder.class);
        example.createCriteria().andEqualTo("procDefKey", prcDefKey);
        wfOrderMapper.deleteByExample(example);
    }

}
